package com.raptor.mongodb.spring;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.raptor.mongodb.entity.MongodbConfigBean;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Order(1)
@ComponentScan("com.shihang.core.mongodb.*")
@EnableAutoConfiguration(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
public class MongodbApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {


    private org.slf4j.Logger logger = LoggerFactory.getLogger(MongodbApplicationContextInitializer.class);


    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) configurableApplicationContext.getBeanFactory();
        ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        List<MongodbConfigBean> list = new ArrayList<>();
        Object[] objects = propertySources.stream().toArray();

        if (objects == null || objects.length == 0) {
            throw new RuntimeException("读取配置文件失败!");
        }

        for (int o = 0; o < objects.length; o++) {
            Object target = objects[o];
            if (!(target instanceof PropertiesPropertySource)) {
                continue;
            }
            PropertiesPropertySource targetPropertySource = (PropertiesPropertySource) target;
            String[] propertyNames = targetPropertySource.getPropertyNames();
            if (propertyNames == null || propertyNames.length == 0) {
                continue;
            }
            List<String> instanceList = this.getInstanceList(propertyNames);
            list = getConfigBeanList(instanceList, propertyNames, targetPropertySource);

        }

        this.registerBeanToSpring(beanFactory, list);
    }


    private void registerBeanToSpring(BeanDefinitionRegistry beanFactory, List<MongodbConfigBean> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        for (int l = 0; l < list.size(); l++) {
            MongodbConfigBean bean = list.get(l);
            String instance = bean.getInstance();
            String host = bean.getHost();
            String port = bean.getPort();
            String database = bean.getDatabase();
            if (StringUtils.isEmpty(host) || StringUtils.isEmpty(port) || StringUtils.isEmpty(database) || StringUtils.isEmpty(instance)) {
                throw new RuntimeException("illeagle field");
            }
            String defaultOptions = new StringBuilder("/?")
                    .append("connectTimeoutMS=").append(bean.getConnectTimeoutMS())
                    .append("&socketTimeoutMS=").append(bean.getSocketTimeoutMS())
                    .append("&maxPoolSize=").append(bean.getMaxPoolSize())
                    .append("&minPoolSize=").append(bean.getMinPoolSize())
                    .append("&maxIdleTimeMS=").append(bean.getMaxIdleTimeMS()).toString();
            String connectionString = new StringBuilder("mongodb://").append(host).append(":").append(port).append(defaultOptions).toString();
            MongoClient client = MongoClients.create(connectionString);
            SimpleMongoClientDatabaseFactory dynamicMongoDbFactory = new SimpleMongoClientDatabaseFactory(client, database);
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MongoTemplate.class);
            beanDefinitionBuilder.addConstructorArgValue(dynamicMongoDbFactory);
            if (l == 0) {
                beanDefinitionBuilder.setPrimary(Boolean.TRUE);
            }
            BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
            beanFactory.registerBeanDefinition(instance, beanDefinition);
        }
    }


    private List<String> getInstanceList(String[] propertyNames) {
        String prefix = new StringBuilder("raptor.mongodb.").toString();
        List<String> list = new ArrayList<>();
        for (int i = 0; i < propertyNames.length; i++) {
            String key = propertyNames[i];
            if (!key.startsWith(prefix)) {
                continue;
            }
            String[] keys = key.split(prefix);
            if (keys == null || keys.length != 2) {
                continue;
            }
            //shihang.mongodb.monitor.host
            String[] instanceArray = keys[1].split("\\.");
            if (instanceArray == null || instanceArray.length != 2) {
                continue;
            }
            String instance = instanceArray[0];
            if (list.contains(instance)) {
                continue;
            }
            list.add(instance);
        }
        return list;
    }


    private List<MongodbConfigBean> getConfigBeanList(List<String> instanceList, String[] propertyNames, PropertiesPropertySource targetPropertySource) {

        if (instanceList == null || instanceList.size() == 0) {
            return Collections.EMPTY_LIST;
        }
        int length = instanceList.size();
        List<MongodbConfigBean> configBeanList = new ArrayList<MongodbConfigBean>();
        String prefix = new StringBuilder("raptor.mongodb").toString();
        for (int ii = 0; ii < length; ii++) {
            String instance = instanceList.get(ii);
            String keyPrefix = new StringBuilder(prefix).append(".").append(instance).toString();
            MongodbConfigBean bean = this.getConfigBean(instance, propertyNames, keyPrefix, targetPropertySource);
            configBeanList.add(bean);
        }
        return configBeanList;
    }


    private MongodbConfigBean getConfigBean(String instance, String[] propertyNames, String keyPrefix, PropertiesPropertySource targetPropertySource) {
        MongodbConfigBean bean = new MongodbConfigBean();
        bean.setInstance(instance);
        for (int i = 0; i < propertyNames.length; i++) {
            String key = propertyNames[i];
            if (!key.startsWith(keyPrefix)) {
                continue;
            }
            String keyPrefixsplit = new StringBuilder(keyPrefix).append(".").toString();
            String[] fieldArray = key.split(keyPrefixsplit);
            if (fieldArray == null || fieldArray.length != 2) {
                return bean;
            }
            String field = fieldArray[1];
            switch (field.toLowerCase()) {
                case "host":
                    String host = (String) targetPropertySource.getProperty(key);
                    bean.setHost(host);
                    break;
                case "port":
                    String port = (String) targetPropertySource.getProperty(key);
                    bean.setPort(port);
                    break;
                case "database":
                    String database = (String) targetPropertySource.getProperty(key);
                    bean.setDatabase(database);
                    break;
                case "connecttimeoutms":
                    String connectTimeoutMS = (String) targetPropertySource.getProperty(key);
                    bean.setConnectTimeoutMS(connectTimeoutMS);
                    break;
                case "sockettimeoutms":
                    String socketTimeoutMS = (String) targetPropertySource.getProperty(key);
                    bean.setSocketTimeoutMS(socketTimeoutMS);
                    break;
                case "maxpoolsize":
                    String maxPoolSize = (String) targetPropertySource.getProperty(key);
                    bean.setMaxPoolSize(maxPoolSize);
                    break;
                case "minpoolsize":
                    String minPoolSize = (String) targetPropertySource.getProperty(key);
                    bean.setMinPoolSize(minPoolSize);
                    break;
                case "maxidletimems":
                    String maxIdleTimeMS = (String) targetPropertySource.getProperty(key);
                    bean.setMaxIdleTimeMS(maxIdleTimeMS);
                    break;
            }
        }
        return bean;
    }


}
